package com.hanwei.uap.acceptors.sstn.server;

import com.hanwei.uap.acceptors.api.channel.ChannelManager;
import com.hanwei.uap.acceptors.api.channel.SessionManager;
import com.hanwei.uap.acceptors.api.constants.ChannelKey;
import com.hanwei.uap.acceptors.api.constants.Server;
import com.hanwei.uap.acceptors.api.service.CommandService;
import com.hanwei.uap.acceptors.api.service.MsgLogService;
import com.hanwei.uap.acceptors.api.service.TimeSynchronizeService;
import com.hanwei.uap.acceptors.api.utils.ChannelUtils;
import com.hanwei.uap.acceptors.api.utils.LogUtils;
import com.hanwei.uap.common.enums.LogType;
import com.hanwei.uap.common.enums.sstn.ChannelType;
import com.hanwei.uap.common.enums.sstn.FrameType;
import com.hanwei.uap.common.util.ByteUtil;
import com.hanwei.uap.common.util.DateUtil;
import com.hanwei.uap.common.util.bytes.SstnUtil;
import com.hanwei.uap.model.po.Command;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;

@ChannelHandler.Sharable
@Component
public class NettyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private static final InternalLogger log = InternalLoggerFactory.getInstance("channel");
    private String serverHost = Server.DEFAULT_BIND_HOST;
    private int serverPort = Server.DEFAULT_BIND_PORT;
    @Autowired
    private Environment env;
    @Autowired
    private SessionManager sessionManager;
    @Autowired
    private ChannelManager channelManager;
    @Autowired
    private TimeSynchronizeService timeSynchronizeService;
    @Autowired
    private MsgLogService msgLogService;
    @Autowired
    private CommandService commandService;

    @PostConstruct
    private void init(){
        if(env.getProperty("uap.server.host")!=null){
            serverHost = env.getProperty("uap.server.host");
        }
        if(env.getProperty("uap.server.port")!=null){
            serverPort = env.getProperty("uap.server.port",Integer.class);
        }
        log.info("handler use serverHost:{},use serverPort:{}",serverHost,serverPort);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String nowTime = DateUtil.formatDate(DateUtil.DF_ALL23, new Date());
        Channel channel = ctx.channel();
        if (log.isInfoEnabled()) {
            String channelLogInfo = LogUtils.channelLogInfo(LogType.CHANNEL_ACTIVE, channel, serverHost, serverPort);
            log.info("{}, ActiveTime:{}, Online Devices:{}", channelLogInfo, nowTime, channelManager.size());
        }
        ChannelUtils.setAttributeIfAbsent(channel, ChannelKey.TIME_ACTIVE, nowTime);
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String nowTime = DateUtil.formatDate(DateUtil.DF_ALL23, new Date());
        Channel channel = ctx.channel();
        if (log.isInfoEnabled()) {
            String channelLogInfo = LogUtils.channelLogInfo(LogType.CHANNEL_INACTIVE, channel, serverHost, serverPort);
            log.info("{}, InActiveTime:{}, Online Devices:{}", channelLogInfo, nowTime, channelManager.size());
        }
        String deviceId = channelManager.remove(channel);
        if(null != deviceId){
            ChannelUtils.setAttributeIfAbsent(channel, ChannelKey.TIME_INACTIVE, nowTime);
            String sessionData = sessionManager.offlineSyn(channel);
            if (log.isInfoEnabled()) {
                String channelLogInfo = LogUtils.channelLogInfo(LogType.SESSION_OFFLINE, channel, serverHost, serverPort);
                log.info("{}, push session data to redis:{}!", channelLogInfo, sessionData);
            }
        }
        super.channelInactive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        String receiveTime = DateUtil.formatDate(DateUtil.DF_ALL23, new Date());
        Channel channel = ctx.channel();
        byte[] data = new byte[msg.readableBytes()];
        msg.readBytes(data);
        String hexData = ByteUtil.byte2hex(data);
        if (log.isInfoEnabled()) {
            String channelLogInfo = LogUtils.channelLogInfo(LogType.DATA_ORIGINAL, channel, serverHost, serverPort);
            log.info("{}, the original data length:{}, the data hex:{}", channelLogInfo, data.length, hexData);
        }
        data = SstnUtil.untransfer(data);
        if (data[0] != 0x3A || data[data.length - 1] != 0x0D) {
            String channelLogInfo = LogUtils.channelLogInfo(LogType.DATA_UNVALID, channel, serverHost, serverPort);
            log.warn("{}, unvalid data,it not starts with 0x3a and ends with 0x0d,channel will be closed!",channelLogInfo);
            ctx.close();
        } else {
            byte[] deviceIdBytes = ByteUtil.arraycopy(data, 1, 5);
            String deviceId = ByteUtil.byte2hex(deviceIdBytes, true);
            byte[] deviceFullIdBytes = ByteUtil.arraycopy(data, 1, 10);
            String deviceFullId = ByteUtil.byte2hex(deviceFullIdBytes);
            int deviceType = ByteUtil.byte2int(data[10], data[9]);
            int channelNum = ByteUtil.byte2int(data[12], data[11]);
            FrameType frameType = FrameType.getFrameTypeByValue(channelNum);
            AttributeKey<String> deviceIdKey = AttributeKey.valueOf(ChannelKey.DEVICEID);
            Attribute<String> deviceIdAttr = channel.attr(deviceIdKey);
            if(deviceIdAttr.get()==null) {
                deviceIdAttr.set(deviceId);
                ChannelUtils.setDeviceType(channel, ChannelKey.DEVICETYPE, deviceType);
                ChannelUtils.setDeviceFullId(channel, ChannelKey.DEVICEFULLID, deviceFullId);
                channelManager.removeAndClose(deviceId);
                channelManager.add(deviceId, channel);
                String sessionData = sessionManager.onlineSyn(channel);
                if (log.isInfoEnabled()) {
                    String channelLogInfo = LogUtils.channelLogInfo(LogType.SESSION_ONLINE, channel, serverHost, serverPort);
                    log.info("{}, push session data to redis:{}!", channelLogInfo, sessionData);
                }
            }

            // 向客户端发送消息
            int channelType = data[11] & 0x03;
            if (ChannelType.getChannelTypeByValue(channelType) == ChannelType.CLIENT_REQ) {
                byte[] echoBytes = SstnUtil.getReplyData(data);
                ByteBuf response = Unpooled.wrappedBuffer(echoBytes);
                channel.writeAndFlush(response);
                if (log.isInfoEnabled()){
                    String channelLogInfo = LogUtils.channelLogInfo(LogType.SERVER_REPLY, channel, serverHost, serverPort);
                    log.info("{}, response:{}", channelLogInfo, ByteUtil.byte2hex(echoBytes));
                }
            }

            if(log.isInfoEnabled()){
                String channelLogInfo = LogUtils.channelLogInfo(LogType.DATA_INFO, channel, serverHost, serverPort);
                log.info("{}, DeviceType:{}, ChannelNum:{}, FrameType:{}", channelLogInfo, deviceType, channelNum, frameType);
            }
            msgLogService.pushToMq(channel, frameType,deviceId,hexData,deviceType,serverHost,serverPort, receiveTime,channelNum);
            if(log.isInfoEnabled()){
                String channelLogInfo = LogUtils.channelLogInfo(LogType.DATA_PUT2MQ, channel, serverHost, serverPort);
                log.info("{}, Save Data To MQ Success.", channelLogInfo, deviceType, channelNum, frameType);
            }
            //同步时间
            if(timeSynchronizeService.canSyncTime(deviceId) || frameType == FrameType.BootStrapFrame){
                timeSynchronizeService.singleSyncTime(channel);
                if(log.isInfoEnabled()){
                    String channelLogInfo = LogUtils.channelLogInfo(LogType.SERVER_COMMAND, channel, serverHost, serverPort);
                    log.info("{}, Send SyncTime Command.", channelLogInfo);
                }
            }
            //发送指令
            Command command = commandService.getCommand(deviceId);
            if(null!=command){
                channelManager.sendCommand(command);
                if(log.isInfoEnabled()){
                    String channelLogInfo = LogUtils.channelLogInfo(LogType.SERVER_COMMAND, channel, serverHost, serverPort);
                    log.info("{}, Send Asyn Command.", channelLogInfo);
                }
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        Channel channel = ctx.channel();
        String channelLogInfo = LogUtils.channelLogInfo(LogType.CHANNEL_EXCEPTION, channel, serverHost, serverPort);
        log.warn(channelLogInfo + ", catch exception and the channel will be closed!", cause);
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                Channel channel = ctx.channel();
                String channelLogInfo = LogUtils.channelLogInfo(LogType.CHANNEL_TIMEOUT, channel, serverHost, serverPort);
                log.warn(channelLogInfo + ", the channel is timeout and will be closed!");
                ctx.close();
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }
    }
}